package devserv

import (
	"config"
	"encoding/json"
	"fmt"
	"github.com/garyburd/redigo/redis"
	"github.com/gorilla/websocket"
	"log"
	"rhelper"
	"strings"
	"time"
)

type ControllerMsgDispacher interface {
	OnJSONMessage(msgHead *Dev2ServMsgHead, message []byte)
	OnUserRequest(message []byte) error
}

const FilterLifeTotaly = 1800

var (
	CONTROLLER_APP_TYPE_PURIFIER_MEASURE = "purifier-home"
	DATE_UTC_FORMAT                      = "20060102150405"

	luaScript = `local value = redis.call('HGET', KEYS[1], 'key')
	              if tonumber(KEYS[2]) == tonumber(value) then
	              local serverId = redis.call('HGET', KEYS[1], 'serverId')
	              redis.call('HSET', KEYS[1], 'send', '1')
	              return serverId
	              else
	              redis.call('HMSET', KEYS[1], 'key', KEYS[2], 'serverId', KEYS[3], 'cid', KEYS[4])
	              return ""
	              end`
)

type Controller struct {
	ws         *websocket.Conn
	chanx      chan bool
	dispatcher ControllerMsgDispacher

	Id      string `json:"id"`
	Account string `json:"account"`
	CType   string `json:"type"`
	Apptype string `json:"apptype"`

	DeviceName        string `json:"deviceName"`
	DeviceVersion     string `json:"deviceVersion"`
	DeviceVersionCode int    `json:"deviceVersionCode"`

	FirmName        string `json:"firmName"`
	FirmVersion     string `json:"firmVersion"`
	FirmVersionCode int    `json:"firmVersionCode"`

	Key    int `json:"key"`
	Relay  int `json:"relay"`
	MaxPwm int `json:"maxPwm"`
	wait   chan<- []byte

	PowerState int    `json:"powerState"`
	Delay      int    `json:"delay"`
	TimerList  string `json:"timerList"`

	ImgName string `json:"imgName"`
}

func (c *Controller) onJSONMessage(msgHead *Dev2ServMsgHead, message []byte) {
	c.dispatcher.OnJSONMessage(msgHead, message)
}

func (c *Controller) onUserRequest(message []byte,
	wait chan<- []byte) error {
	if c.wait != nil {
		return fmt.Errorf("controller is busy now")
	}

	err := c.dispatcher.OnUserRequest(message)
	if err == nil {
		c.wait = wait
		//wait = c.wait
	}

	return err
}

func (c *Controller) WriteJSONWait(json interface{}, wait chan<- []byte) error {
	if c.wait != nil {
		return fmt.Errorf("controller is busy now")
	}

	err := c.SendJSON(json)
	if err == nil {
		c.wait = wait
	}

	return err
}

func (c *Controller) IsBusy() bool {
	return c.wait != nil
}

func (c *Controller) GiveupWaitAndDiscard() {
	c.wait = nil

	if c.ws != nil {
		log.Println("controller not response to user request, consider it as offline, id:", c.Id)
		c.ws.Close()
	}

	rconn := rhelper.Pool().Get()
	defer rconn.Close()

	rconn.Do("SREM", rhelper.OnlineDev, rhelper.ControllerHash+c.Id)
}

func (c *Controller) save2DB(ip string) {
	rconn := rhelper.Pool().Get()
	defer rconn.Close()

	uniqueId, err := redis.String(rconn.Do("HGET", rhelper.AccountHash+c.Account, "id"))
	if err != nil || uniqueId == "" {
		log.Printf("get user unique id failed:%s,account:%s\n", err.Error(), c.Account)
		return
	}

	accControllerStr, err := redis.String(rconn.Do("HGET", rhelper.UserHash+uniqueId, "controllers"))
	if err != nil {
		log.Println("save2DB, get account controller string error:", err)
		accControllerStr = ""
	}

	accControllerStr = rhelper.CommaStringAppend(accControllerStr, c.Id)

	homeId, err := redis.String(rconn.Do("HGET", rhelper.ControllerHash+c.Id, "homeId"))
	if err != nil {
		log.Println("save2Db, get controller home id error:", err)
		homeId = ""
	}

	accountFromDB, _ := c.loadAccountFromDB()

	if homeId != "" && accountFromDB != c.Account {
		devices, err := redis.String(rconn.Do("HGET", rhelper.HomeHash+homeId, "devices"))
		log.Println("HGET", rhelper.HomeHash+homeId, "devices")
		if err != nil {
			log.Println("get home devices failed, get home devices string error:", err)
		}
		devices = rhelper.CommaStringDelete(devices, rhelper.ControllerHash+c.Id)

		rconn.Do("HSET", rhelper.HomeHash+homeId, "devices", devices)
		rconn.Do("HDEL", rhelper.ControllerHash+c.Id, "homeId")
	}

	t := time.Now().UTC()
	rconn.Send("MULTI")
	rconn.Send("HMSET", rhelper.ControllerHash+c.Id,
		"id", c.Id, "account", c.Account, "type", c.CType, "apptype", c.Apptype,
		"deviceName", "Levoi Smart True HEPA Air Purifier", "deviceVersion", c.DeviceVersion, "deviceVersionCode", c.DeviceVersionCode,
		"firmName", c.FirmName, "firmVersion", c.FirmVersion, "firmVersionCode", c.FirmVersionCode, "lastLoginTime",
		t.Format("20060102150405"), "lastLoginIp", ip, "relay", c.Relay, "status", "online", "serverId", config.ServerId,
		"timerList", c.TimerList, "maxPwm", c.MaxPwm, "alias", "Levoi Smart True HEPA Air Purifier", "brand", "Levoit", "modelNo", "LV-PUR131S",
		"imgName", "purifiers.png")

	rconn.Send("HSET", rhelper.UserHash+uniqueId, "controllers", accControllerStr)

	_, err = rconn.Do("EXEC")
	if err != nil {
		log.Println("save2DB failed, save controller data error:", err)
	}
}

/*
func (c *Controller) savePowerReport(msg PowerReportMsg) {
	rconn := rhelper.Pool().Get()
	defer rconn.Close()

	currentTime := time.Now().UTC().Format("2006010215")

	p, err := redis.String(rconn.Do("HGET", rhelper.PowerReport+c.Id, currentTime))
	if err != nil && err != redis.ErrNil {
		p = ""
	}

	p = rhelper.CommaReportAppend(p, msg.Energy, msg.Duration)
	_, err = rconn.Do("HSET", rhelper.PowerReport+c.Id, currentTime, p)
	if err != nil {
		log.Println("savePowerReport failed, error:", err)
	}

}
*/

func getReportDateTime(report string) string {
	if report == "" {
		return ""
	}

	array := strings.Split(report, ":")
	if len(array) > 0 {
		return array[0]
	}

	return ""
}

func (c *Controller) SavePowerReportNew(msg PowerReportMsg) {
	log.Println("SavePowerReportNew..")

	rconn := rhelper.Pool().Get()
	defer rconn.Close()

	length, err := redis.Int(rconn.Do("LLEN", rhelper.PowerReport+c.Id))
	if err != nil {
		log.Println("get powerReport list length failed, error:", err)
	}

	var lastReport string
	if length > 0 {
		lastReport, err = redis.String(rconn.Do("LINDEX", rhelper.PowerReport+c.Id, length-1))
		if err != nil {
			log.Println("load last power report failed, error:", err)
		}
	}

	lastTime := getReportDateTime(lastReport)
	currentTime := time.Now().UTC().Format("2006010215")

	currentReport := rhelper.CommaReportAppend(lastReport, currentTime, msg.Energy, msg.Duration)

	if currentTime == lastTime {
		_, err = rconn.Do("LSET", rhelper.PowerReport+c.Id, length-1, currentReport)
		if err != nil {
			log.Println("savePowerReport failed, error:", err)
		}
	} else {
		_, err = rconn.Do("RPUSH", rhelper.PowerReport+c.Id, currentReport)
		if err != nil {
			log.Println("savePowerReport failed, error:", err)
		}
	}
}

func (c *Controller) createDispatcher() bool {
	var dispatcher ControllerMsgDispacher = nil
	switch c.Apptype {
	case CONTROLLER_APP_TYPE_PURIFIER_MEASURE:
		dispatcher = &SwitchMeasureDispatcher{controller: c}
		break
	default:
		log.Println("no message dispatcher found for app type:", c.Apptype)
		break
	}

	if dispatcher != nil {
		c.dispatcher = dispatcher
		return true
	}
	return false
}

func (c *Controller) SendMessage(message []byte) error {
	err := c.ws.WriteMessage(websocket.TextMessage, message)
	return err
}

func (c *Controller) SendJSON(json interface{}) error {
	err := c.ws.WriteJSON(json)
	return err
}

func (c *Controller) loadAccountFromDB() (acc string, group string) {
	rconn := rhelper.Pool().Get()
	defer rconn.Close()

	group = ""
	acc = ""

	fields, err := redis.Strings(rconn.Do("HMGET", rhelper.ControllerHash+c.Id, "account", "group"))
	if err != nil && err != redis.ErrNil {
		log.Println("loadAccountFromDB, get account controller string error:", err)
		return
	}

	group = fields[1]
	acc = fields[0]
	return
}

func (c *Controller) CloseWebSocket() {

	if c.ws != nil {
		c.ws.Close()
	}
}

func (c *Controller) setDeviceOfflineTime() {
	t := time.Now()

	rconn := rhelper.Pool().Get()
	defer rconn.Close()
	rconn.Do("HSET", rhelper.ControllerHash+c.Id, "offlineTime", t.UTC().Format(DATE_UTC_FORMAT))
}

func (c *Controller) add2OnlineDev() {
	rconn := rhelper.Pool().Get()
	defer rconn.Close()

	rconn.Send("MULTI")
	rconn.Send("SADD", rhelper.OnlineDev, rhelper.ControllerHash+c.Id)
	rconn.Send("EXPIRE", rhelper.OnlineDev, 150)
	_, err := rconn.Do("EXEC")
	if err != nil {
		log.Println("add2OnlineDev failed, error:", err)
	}
}

// if wait key have been exist, return user server name
func checkWaitKey(account string, key int, cid string) string {
	rconn := rhelper.Pool().Get()
	defer rconn.Close()

	log.Println("SET wait key", rhelper.WaitKey+account, key, config.ServerId, cid)
	var getScript = redis.NewScript(4, luaScript)
	serverName, err := redis.String(getScript.Do(rconn, rhelper.WaitKey+account, key, config.ServerId, cid))
	if err != nil {
		log.Println("checkWaitKey err: ", err)
		return ""
	}

	return serverName
}

func (c *Controller) onWaitKeyResp() {
	serverName := checkWaitKey(c.Account, c.Key, c.Id)
	if serverName == "" {
		return
	}

	type WaitKeyResp struct {
		Uri     string `json:"uri"`
		Account string `json:"account"`
		Key     int    `json:"key"`
	}

	var waitKeyResp WaitKeyResp
	waitKeyResp.Account = c.Account
	waitKeyResp.Key = c.Key
	waitKeyResp.Uri = "/waitKey"

	buf, _ := json.Marshal(waitKeyResp)
	publishMessage(serverName, c.Id, buf)
}

func (c *Controller) updateControllerRelay(relay string) {
	if relay == "break" {
		discardLeftOnWait(c.Id)
	}

	rconn := rhelper.Pool().Get()
	defer rconn.Close()

	rconn.Do("HSET", rhelper.ControllerHash+c.Id, "relay", relay)
}

func (c *Controller) onUserRequestWithServerCmd(userRequest *UserRequest) {
	switch userRequest.ServerCmd {
	case "closeWebSocket":
		c.CloseWebSocket()
		break
	case "giveUpWaite":
		c.GiveupWaitAndDiscard()
		break
	case "leftOnNotify":
		discardLeftOnWait(c.Id)
		go waitLeftOnNotify(c.Id)
		break
	default:
		break
	}
}
